package ferp.core.mode;

import ferp.core.card.Card;
import ferp.core.game.Game;
import ferp.core.game.Input;
import ferp.core.game.Settings;
import ferp.core.log.Log;
import ferp.core.player.Player;
import ferp.core.player.Profile;
import ferp.core.state.State;
import ferp.core.tutorial.Scenario;

import java.util.HashMap;

/**
 * User: olegs
 * Date: 19/05/11 14:51
 */
public abstract class Mode
{
  // get mode by name
  public static Mode get(String name) {return modes.get(name);}
  // moving?
  public static boolean isMoving(Mode mode) {return mode == misere || mode == passing || mode == tricking || mode == tricking10;}

  // new mode must register itself in the static hash table
  public Mode() {modes.put(getClass().getName(), this);}

  // mode initialization
  public int initialize(Game.Listener listener, Profile profile, Game g, Settings settings) throws Game.Error
  {
    // reset rounds counter
    g.rounds = 0;
    // reset hand
    g.hand = 0;
    // setup the starting player for this round
    g.player.starting = getStartingPlayer(g);

    return State.Result.SCHEDULE;
  }

  public final int fill(Game.Listener listener, Game g, Settings settings, Input input) throws Game.Error
  {
    // check for the round solve
    if (g.hand == 0)
    {
      // this should fire specific event (listener.onBiddingRoundStart())
      prepare(listener, g, settings);
      // set the current player to be the first player in round
      g.player.current = getFirstPlayer(g);
    }

    Log.debug(Log.TAG, g.player(g.player.current) + ": getting input options");

    int r = options(listener, g, settings, input);

    if (g.tutorial())
    {
      boolean human = Player.isHuman(mover(g, settings, g.player.current));
      Scenario.Note note = getOptionsNote(g, settings);

      if (note == null)
      {
        if (!human)
          r = State.Result.SCHEDULE;
      }
      else
      {
        // wait for touch only for android players
        if (!human)
          g.options.set(Input.Options.WAIT_FOR_TOUCH);

        listener.showNote(note);
      }

      // mark allowed input option if human is mover in tutorial mode
      if (human)
        mark(listener, g);
    }

    return r;
  }

  // processing
  public final int process(Game.Listener listener, Game g, Settings settings, Input input) throws Game.Error
  {
    // first verify that input is valid in case of tutorial - if it's not, do not advance the game (act as 'STAY')
    if (g.tutorial() && Player.isHuman(mover(g, settings, g.player.current)) && !validate(listener, g, settings, input))
      return State.Result.SCHEDULE;

    // call the actual processing
    switch (act(listener, g, settings, input))
    {
      case NEXT:
        // switch to the next player or exit this round (going to "summarizing" state)
        if (isRoundOver(g))
          exit(g);
        else
          next(g);
        break;

      case STAY:
        break;

      case DONE:
        done(listener, g, settings);
        break;
    }

    return State.Result.SCHEDULE;
  }

  // chill down after round is over but before summarizing
  public int chill(Game.Listener listener, Game g, Settings settings) {return State.Result.SCHEDULE;}

  // we got here when the round is over
  public final int summarize(Game.Listener listener, Game g, Settings settings)
  {
    // analyze round results
    analyze(listener, g, settings);

    // increment rounds counter
    ++g.rounds;
    // reset the hand counter
    g.hand = 0;

    // check if the set is over as well
    if (isSetOver(g))
    {
      if (g.tutorial())
      {
        Scenario.Note note = getSetOverNote(g, settings);

        if (note != null)
        {
          listener.showNote(note);

          g.options.set(Input.Options.WAIT_FOR_TOUCH);
        }
      }

      done(listener, g, settings);
    }

    return g.options.isHumanInputRequired() ? State.Result.SHOW_OPTIONS : State.Result.SCHEDULE;
  }
  // get current mover (a player who plays a card)
  public Player mover(Game g, Settings settings, int current)
  {
    // by default it's a current player
    return g.player(current);
  }

  public final void exit(Game g) {g.set(State.chill);}

  public void score(Game g, Settings settings) {Log.debug(Log.TAG, "Score method called for wrong mode " + getClass().getName());}

  public void show(Game.Listener listener, Game g, Settings settings)
  {
    Input.Options options = g.options;

    listener.onBeforeShowInputOptions(g, settings);

    // this option suppresses other input options (used in tutorial mode)
    if (options.isSet(Input.Options.WAIT_FOR_TOUCH))
      return;

    if (options.isSet(Input.Options.PLAY))
      listener.showBidPlay(options.bid);

    if (options.isSet(Input.Options.MISERE))
      listener.showBidMisere();

    if (options.isSet(Input.Options.PASS))
    {
      if (g.bid != null || settings.isRostov())
        listener.showBidPass();
      else
        listener.showBidPass(ferp.core.calc.scorer.Passing.getTrickCost(g, settings));
    }

    if (options.isSet(Input.Options.DROP))
      listener.showDrop();

    if (options.isSet(Input.Options.WITHOUT3))
      listener.showWithout3();

    if (options.isSet(Input.Options.DECLARE))
      listener.showContractPlay(g, options.bid);

    if (options.isSet(Input.Options.WHIST_FULL))
      listener.showWhistFull();

    if (options.isSet(Input.Options.WHIST_HALF))
      listener.showWhistHalf();

    if (options.isSet(Input.Options.WHIST_PASS))
      listener.showWhistPass();

    if (options.isSet(Input.Options.STAND))
      listener.showOpenStand();

    if (options.isSet(Input.Options.TRICKING_10_CONFIRM))
      listener.showTricking10Confirm();

    if (options.isSet(Input.Options.MISERE_CONFIRM))
      listener.showMisereConfirm();

    if (options.isOfferSet())
      listener.showOffer();

    if (options.hand != 0)
      listener.enableCards(g.player.current, options.hand);
  }

  public Card.State getTalonCardState(Game g, Settings settings, int i) {return Card.State.FACE_DOWN;}

  // determines whether 'first player' must be shown, descendant implementations must take into account
  // cases when turn/clock images are shown over the image
  public boolean showFirstHand(Game g, Settings settings) {return true;}

  // mark allowed input option in tutorial mode
  public void mark(Game.Listener listener, Game g) {/* do nothing by default */}

  // 'act' method results
  protected enum Act {NEXT, STAY, DONE}

  // get starting player - by default it's the player next to the dealer
  protected int getStartingPlayer(Game g) {return Game.next(g.player.dealer);}
  // called before new round starts
  protected void prepare(Game.Listener listener, Game g, Settings settings) {/* do nothing by default */}
  // get first player - by default it's the starting player
  protected int getFirstPlayer(Game g) {return g.player.starting;}
  // set input options
  protected abstract int options(Game.Listener listener, Game g, Settings settings, Input input) throws Game.Error;
  // perform the actual work depending on the specific mode
  protected Act act(Game.Listener listener, Game g, Settings settings, Input input) throws Game.Error {return Act.NEXT;}
  // analyze round result
  protected void analyze(Game.Listener listener, Game g, Settings settings) {/* do nothing by default */}
  // return true if round is over
  protected boolean isRoundOver(Game g) {return g.hand >= 2;}
  // return true if set is over (true by default)
  protected boolean isSetOver(Game g) {return true;}
  // called after set ends
  protected void done(Game.Listener listener, Game g, Settings settings) {/* do nothing by default */}
  // move to the next player
  protected void next(Game g)
  {
    // increment hand
    g.hand = ++g.hand % (Game.PLAYERS + 1);
    // next player
    g.player.current = Game.next(g.player.current);
  }

  // check if input is valid in case of tutorial
  protected boolean validate(Game.Listener listener, Game g, Settings settings, Input input) {return true;}

  // 'option' comment in tutorial mode
  protected Scenario.Note getOptionsNote(Game g, Settings settings) {return null;}
  // 'round over' comment in tutorial mode
  protected Scenario.Note getRoundOverNote(Game g, Settings settings, int round) {return null;}
  // 'set over' comment in tutorial mode
  protected Scenario.Note getSetOverNote(Game g, Settings settings) {return null;}

  // modes hash map
  private static HashMap<String, Mode> modes = new HashMap<String, Mode>();

  // modes
  public static final Dealing dealing = new Dealing();
  public static final Bidding bidding = new Bidding();
  public static final Dropping dropping = new Dropping();
  public static final Contracting contracting = new Contracting();
  public static final MisereConfirm misereConfirm = new MisereConfirm();
  public static final Misere misere = new Misere();
  public static final Passing passing = new Passing();
  public static final Tricking tricking = new Tricking();
  public static final Tricking10 tricking10 = new Tricking10();
}
